﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Threading;
using System.IO;

namespace PacketPasser
{
    public class PacketProxy : IDisposable
    {
        readonly TcpListener _gamelistener;
        TcpClient _gameClient;
        readonly Task _listenerTask;
        readonly Task _ConsoleTask;
        readonly string _destinationHostname;
        readonly int _destinationPort;
        readonly int _listenerPort;
        Thread _ignoreThread;

        const int bufferSize = 40960;

        bool stopping;
        bool stopped;
        public bool IgnoreFirst { get; set; }
        public bool VisitPackets { get; set; }
        public bool FilterPackets { get; set; }
        public bool Connected { get; private set; }

        public void Stop()
        {
            stopping = true;
            while (!stopping || _listenerTask.Status == TaskStatus.Running || _ConsoleTask.Status == TaskStatus.Running)
                System.Threading.Thread.Sleep(400);
            _gameClient.Close();

            _gamelistener.Stop();

        }
        void StartProxy()
        {
            _gameClient = new TcpClient()
            {
                ReceiveBufferSize = bufferSize,
                SendBufferSize = bufferSize,
            };


            _listenerTask.Start();

            _ConsoleTask.Start();
        }
        public PacketProxy(int listenPort, string destinationHostname, int destinationPort)
        {
            _destinationHostname = destinationHostname;
            _destinationPort = destinationPort;
            _listenerPort = listenPort;
            _listenerTask = new Task(Listen);
            _ConsoleTask = new Task(ProcessPackets);
            IgnoreFirst = true;
            _gamelistener = new TcpListener(listenPort);
            _gamelistener.Start();
            Console.WriteLine("Listener Active on port " + listenPort);
            Console.WriteLine("Directing traffic to " + _destinationHostname + ":" + _destinationPort);

            StartProxy();
        }
        void IgnoreFirstConnection()
        {
            Console.WriteLine("Ignoring first connection attempt");
            bool foundFirstPing = false;
            while (!stopping && !foundFirstPing && IgnoreFirst)
            {

                System.Threading.Thread.Sleep(100);
                if (!foundFirstPing && _gamelistener.Pending() && IgnoreFirst)// for the initial server ping connection
                {
                    foundFirstPing = true;
                    var pingClient = _gamelistener.AcceptTcpClient();
                 
                }
                System.Threading.Thread.Sleep(10);
            }
        }

        void Listen()
        {

            IgnoreFirstConnection();
            if (stopping)
            {
                Console.WriteLine("stopping");
                return;
            }
            Console.WriteLine("Connection attempt, accepting");
            var client = _gamelistener.AcceptTcpClient();
            client.SendBufferSize = bufferSize;
            client.ReceiveBufferSize = bufferSize;
            client.NoDelay = true;

            Console.WriteLine("Connection established");
            var clientStream= client.GetStream();

            _gameClient.Connect(_destinationHostname, _destinationPort);
            var serverStream = _gameClient.GetStream();
            _proxy = new MinecraftProxy();
            Connected = _gameClient.Connected && client.Connected;
            while (!stopping && _gameClient.Connected && client.Connected)
            {
               
                    PassPackets(clientStream, serverStream, _proxy.VisitPacket);
                
                System.Threading.Thread.Sleep(10);
            }
            Console.WriteLine("Closing connection(s)");
            client.Close();

            stopped = true;
        }

        private MinecraftProxy _proxy;
        
        void ProcessPackets()
        {
            while (stopping == false)
            {
                if (_proxy == null)
                {
                    System.Threading.Thread.Sleep(20);
                    continue;
                }
                byte[] packetCopy;
                if (!PacketsRecieved.TryDequeue(out packetCopy))
                    continue;
                _proxy.ProcessPacket(packetCopy);


                System.Threading.Thread.Sleep(10);
            }
        }

        ThreadLocal<byte[]> _serverBuffer = new ThreadLocal<byte[]>(() => new byte[bufferSize]);
        ThreadLocal<byte[]> _clientBuffer = new ThreadLocal<byte[]>(() => new byte[bufferSize]);

        System.Collections.Concurrent.ConcurrentQueue<byte[]> PacketsRecieved =
            new System.Collections.Concurrent.ConcurrentQueue<byte[]>();


        void PassPackets(NetworkStream clientstream, NetworkStream serverStream, Func<byte[],int,bool> processMethod)
        {
            
            bool clientData = clientstream.DataAvailable;
            if (clientData)
            {
                int packetLength = clientstream.Read(_clientBuffer.Value, 0, bufferSize);
                serverStream.Write(_clientBuffer.Value, 0, packetLength);
                serverStream.Flush();
            }

            bool serverData = serverStream.DataAvailable;
            if (serverData)
            {
                int packetLength = serverStream.Read(_serverBuffer.Value, 0, bufferSize);

                serverStream.Flush();
                
                if (processMethod == null || VisitPackets==false)
                {
                    
                    clientstream.Write(_serverBuffer.Value, 0, packetLength);
                    clientstream.Flush();
                    return;
                }

                var processResult = processMethod(_serverBuffer.Value,packetLength);
                if ( FilterPackets==false ||processResult)
                {
                   
                        clientstream.Write(_serverBuffer.Value, 0, packetLength);
                        clientstream.Flush();
                   
                    
                }


            }


        }




        #region IDisposable Members

        public void Dispose()
        {
            _gameClient.Close();
            _gamelistener.Stop();
            _ConsoleTask.Dispose();
            _listenerTask.Dispose();

        }

        #endregion

        public int ListenPort { get { return _listenerPort; } }

        public string DestinationHost { get { return _destinationHostname; } }

        public int DestinationPort { get { return _destinationPort; } }

        internal PacketProxy CreateFresh()
        {
            if (this.stopped == false)
                this.Stop();
            return new PacketProxy(_listenerPort, _destinationHostname, _destinationPort);
        }

        internal void GetStatus()
        {
            Console.WriteLine("forwarderTask:" + _listenerTask.Status);
            Console.WriteLine("queuedPacketTask:" + _ConsoleTask.Status);
            Console.WriteLine("queue count:" + _serverBuffer.Value.Count());
            Console.WriteLine("gameClient.connect:" + _gameClient.Connected);
            Console.WriteLine("proxying:" + VisitPackets);
            Console.WriteLine("filtering:" + FilterPackets);

        }
    }
}
